home *** CD-ROM | disk | FTP | other *** search
- /*******************************************************************************************
- By R. Mark Fleming
- Copyright (c) 1993, All Rights Reserved.
-
- Two new BBEdit Extensions for speaking the selected text using Apple new Speach Manager.
-
- 1) Speaking
-
- Simple extension that use Apple's new speach manager to 'Talk' the selected text.
-
- 2) Speaking with Dialog
-
- An extensiont that use Apple's new speach manager to 'Talk' the selected text.
-
- The use is presented with a dialog box with some limited control over how text is spoken.
-
- * You can ask it to spell the text or speak the text.
- * You can use the Talk, Stop, Pause, and Continue buttons to control and replay the text.
- * The exit button is used to return to BBEdit.
-
- Things to do...
- * Fix StdTTS component to allow user to select voice, rate, pitch.
- * Save and restore voice setting using BBEdit's preference callbacks
- * Disable Spell/Talk check while talking or make it stop and continue correctly.
-
- History...
- Version 1.00 Release "Speaking sentences" & "Speaking" module.
- Version 1.10 Fix bug were no female voice is installed (error -244).
- Added tracking of text being spoken.
- Merged both modules into on file: "Speaking".
- If option key is depressed, the default is change to spelling selected text.
- Version 1.11 Fix bug when selected text is scrolled into view (messy...)
- Add correct version resource so GetInfo... from finder will display version number.
- Version 1.20 Cleaned up source code to be included with modules.
- Feel free to enhance these modules...but give credit were credit is due.
- (and send me updated source, so I can add it to any changes I do!).
- Version 1.21 Change all NewHandle() calls to callback-Allocate() calls.
- Free memory allocated when error occures when allocating speech channel.
- #ifdef StackDebug code to check stack space problem (OS Error 28).
-
- Known Problems:
- * System Error 28 - Stack has moved into Application heap occurs on several machines.
- I have been unable to prevent this, during the process the speech manager
- is using to start up async speech, this stack is used up.
-
- Cheers, Mark Fleming
-
- 521 Albert Street,
- Kingston, Ontario,
- Canada, K7K 4M5
-
- Phone: (613) 544-3563 Fax: (613) 544-7499
- AppleLink: CDA0621 InterNet: markf@Post.QueensU.CA
- ** The Author of NetDoctor, a Lab and Network Maintainer Package **
-
- #define StackDebug
- *******************************************************************************************/
- #define DialogDisplayed 1 // if define set CODE Resource name to "Speaking with Dialog" id=128
- // if not define set it to "Speaking" id=129
-
-
- #include "ExternalInterface.h"
- #include "DialogUtilities.h"
- #include <SetupA4.h>
- #include <Desk.h>
- #include <Memory.h>
- #include <Components.h>
- #include <Speech.h>
- #include <StdTTS.h>
- #include <OSUtils.h>
- #include <ToolUtils.h>
- #include <BDC.h>
-
- #include <GestaltEqu.h>
- #pragma parameter __D0 Gestalt(__D0,__A1)
- pascal OSErr Gestalt(OSType selector,long *response) = {0xA1AD,0x2288};
- #include <Folders.h>
-
- enum {
- iTalk =1,
- iStop,
- iCont,
- iPause,
-
- iVoice,
- IPitch,
- iRate,
- iCharMode,
- iExit
- };
-
- struct RefSpeak { /* Speech Channel Ref set to point to this */
- ExternalCallbackBlock *callbacks;
- WindowPtr w;
- DialogPtr d;
- long selStart;
- long selEnd;
- long firstChar;
- Boolean done, spellit;
- SpeechStatusInfo SInfo;
- SpeechChannel channel;
- StdTTSParams TTSParams;
- };
-
-
- Boolean SpeechInstalled(ExternalCallbackBlock *callbacks);
- ComponentResult DoSetTTS(struct RefSpeak *RefSpeechStorage);
- OSErr SetTalkOptions(struct RefSpeak *RefSpeechStorage);
- OSErr stop_speech(SpeechChannel channel);
-
- Boolean OptionDown(void);
- Boolean CommandDown(void);
-
- pascal void MyWordCallback (SpeechChannel chan,
- long refCon, long wordPos, short wordLen);
-
- static pascal Boolean filter(DialogPtr d, EventRecord *event, short *item);
- void SetDialogButtons(register DialogPtr d);
- void HandleDialog(Handle text, long selStart, Size size);
-
- static struct RefSpeak *gRefSpeech;
-
- /*******************************************************************************************/
-
- Boolean SpeechInstalled(ExternalCallbackBlock *callbacks)
- { OSErr err;
- long selStart;
-
- err = Gestalt(gestaltSpeechAttr, &selStart);
- if (err == gestaltUndefSelectorErr || err != noErr) { // requires that the Speech Manager be installed.
- callbacks->ReportOSError(err);
-
- return false;
- }
- if (!(selStart && (1L << gestaltSpeechMgrPresent))) { // requires that the Speech Manager be installed.
- callbacks->ReportOSError(gestaltUndefSelectorErr);
- return false;
- }
- return true;
- }
-
- /******************************************************************************************
-
- Use Standard Voice Selection Component if installed to select voice parameters...
-
- This is a modified version of "Speech Media Handler.sea" routine to select voice information.
- I added saving/resoring GrafPort(), & default voice information if no component is present.
- */
- #define kDefaultVoice 1
- ComponentResult DoSetTTS(struct RefSpeak *RefSpeechStorage)
- { Component ttsComponent;
- ComponentInstance ttsCInstance;
- ComponentDescription ttsDesc;
- Point where = {0,0};
- OSErr err;
- ComponentResult result;
-
- ttsDesc.componentType = stdTextToSpeechComponentType;
- ttsDesc.componentSubType = 0;
- ttsDesc.componentManufacturer = 'appl';
- ttsDesc.componentFlags = 0;
- ttsDesc.componentFlagsMask = 0;
-
- ttsComponent = FindNextComponent(nil, &ttsDesc);
-
- if (0 && /* Disable StdTTS component since I did not get it to work */
- CommandDown() && ttsComponent) {
- GrafPtr save_port;
- GetPort(&save_port);
-
- err = GetIndVoice(kDefaultVoice, &RefSpeechStorage->TTSParams.curVoice.voice);
- gRefSpeech->callbacks->ReportOSError(err);
-
- ttsCInstance = OpenComponent(ttsComponent);
- result = StdSpeechDialog(ttsCInstance, &RefSpeechStorage->TTSParams, &where);
- err = CloseComponent(ttsCInstance);
-
- SetPort(save_port);
- } else { /* Select Voice info... */
-
- gRefSpeech->TTSParams.curVoice.voice.creator = 'gala'; /* try to use "Female voice" = 12 */
- gRefSpeech->TTSParams.curVoice.voice.id = (long)12;
- err = GetVoiceDescription(&gRefSpeech->TTSParams.curVoice.voice,
- &gRefSpeech->TTSParams.curVoice, sizeof(VoiceDescription));
-
- if (err == voiceNotFound) { /* Could find selected voice, Use 1st voice. */
- err = GetIndVoice (kDefaultVoice, &gRefSpeech->TTSParams.curVoice.voice);
- gRefSpeech->callbacks->ReportOSError(err);
-
- err = GetVoiceDescription(&gRefSpeech->TTSParams.curVoice.voice,
- &gRefSpeech->TTSParams.curVoice, sizeof(VoiceDescription));
- }
- result = err;
- }
-
- return result;
- } /* End of () */
-
- /******************************************************************************************
-
- 1) If option key is press, select Spell Literals, else use normal talking
-
- 2) Install reference to global variable for this channel
-
- 3) Install word callback routine to track text.
-
- */
-
- OSErr SetTalkOptions(register struct RefSpeak *RefSpeechStorage)
- { OSType t;
- register OSErr err;
-
- t = (!OptionDown()) ? 'NORM' : 'LTRL'; /* Spell letters or talk ... */
-
- err = SetSpeechInfo (RefSpeechStorage->channel, soCharacterMode, &t);
- if (err != noErr) {
- RefSpeechStorage->callbacks->ReportOSError(err);
- }
-
- err = SetSpeechInfo (RefSpeechStorage->channel, soRefCon, gRefSpeech);
- if (err != noErr) {
- RefSpeechStorage->callbacks->ReportOSError(err);
- }
-
- err = SetSpeechInfo (RefSpeechStorage->channel, soWordCallBack, MyWordCallback);
- if (err != noErr) {
- RefSpeechStorage->callbacks->ReportOSError(err);
- }
-
- return err;
- } /* End of () */
-
- /******************************************************************************************
-
- 1) Stop the current speaking at the end of the current word, wait until this has been
- done before returning. Call SystemTask() to give background tasks chance to do things.
- */
-
- OSErr stop_speech(SpeechChannel channel)
- { OSErr err;
-
- err = StopSpeechAt(channel, kEndOfWord);
- if (err != noErr) SysBeep(20);
-
- while (SpeechBusy() > 0)
- SystemTask();
-
- return err;
- } /* End of () */
-
- /*********************************************************************************************
- Enables word callbacks. The callback routine is invoked for each word generated by the
- speech synthesizer just before the word is actually spoken.
-
- This callback can be disabled by passing a nil ProcPtr in the speechInfo parameter.
- The API label for this selector is: soWordCallBack.
- */
-
- pascal void MyWordCallback (SpeechChannel chan,
- long refCon, long wordPos, short wordLen)
- { struct RefSpeak *RefSpeak;
- GrafPtr save_port;
- long stackSp;
-
- stackSp = StackSpace();
-
- #ifdef StackDebug
- { Str15 tmpString;
-
- NumToString(stackSp, tmpString);
- DebugStr(tmpString);
- }
- #endif
-
- if (stackSp > 12000L) {
-
- GetPort(&save_port);
- RefSpeak = (struct RefSpeak *) refCon; /* Get Address of my globals */
- SetPort(RefSpeak->w);
- /* Hilight next work to be spoken */
- RefSpeak->callbacks->SetSelection(RefSpeak->selStart + wordPos, RefSpeak->selStart + wordPos + wordLen, -1);
-
- SetPort(save_port);
- } /* End if Stack Space */
-
- } /* End of () */
-
- Boolean OptionDown(void)
- { KeyMap k;
-
- GetKeys(&k);
- return (BitTst(&k, 61));
-
- /* true if option */
- } /* End of ModifierDown() */
-
- Boolean CommandDown(void)
- { KeyMap k;
-
- GetKeys(&k);
- return (BitTst(&k, 63));
-
- /* true if Command */
- } /* End of CommandDown() */
-
- #ifdef DialogDisplayed
-
- /******************************************************************************************
-
- 1) Update dialog buttons relative to what is currently talking, and call BBEdit's
- window filter routine to do the rest of the need things.
- */
-
- static pascal Boolean filter(DialogPtr d, EventRecord *event, short *item)
- { OSErr err;
- Boolean result;
- long stackSp;
-
-
- SetUpA4();
-
- stackSp = StackSpace();
-
- err = GetSpeechInfo(gRefSpeech->channel, soStatus, &gRefSpeech->SInfo);
-
- SetDialogButtons(d);
-
- result = gRefSpeech->callbacks->StandardFilter(d, event, item);
-
- RestoreA4();
-
- return result;
- } /* End of () */
-
- void SetDialogButtons(register DialogPtr d)
- {
- XAbleDlgCtl(d, iCont, gRefSpeech->SInfo.outputPaused);
- XAbleDlgCtl(d, iTalk, !gRefSpeech->SInfo.outputBusy);
- XAbleDlgCtl(d, iStop, gRefSpeech->SInfo.outputBusy);
- XAbleDlgCtl(d, iPause, !gRefSpeech->SInfo.outputPaused & gRefSpeech->SInfo.outputBusy);
- } /* End of () */
-
- /******************************************************************************************
-
- 1) Place a dialog on the center of the screen.
- 2) start the talk the selected text
- 3) Wait for user to select exit button.
-
- */
-
- void HandleDialog(Handle text, long selStart, Size size)
- { OSErr err;
- DialogPtr d;
- short item;
- Ptr TheTextPtr;
- Handle copy;
-
- OSType t;
- long temp;
-
- Str15 sPitch, sRate;
-
- long stackSp;
-
- stackSp = StackSpace();
-
- #ifdef StackDebug
- { Str15 tmpString;
-
- NumToString(stackSp, tmpString);
- DebugStr(tmpString);
- }
- #endif
-
- GetSpeechPitch (gRefSpeech->channel, &gRefSpeech->TTSParams.pitch);
- temp = FixRound(gRefSpeech->TTSParams.pitch);
-
- NumToString(temp, sPitch);
-
- GetSpeechRate (gRefSpeech->channel, &gRefSpeech->TTSParams.rate);
- temp = FixRound(gRefSpeech->TTSParams.rate);
-
- NumToString(temp, sRate);
- ParamText(gRefSpeech->TTSParams.curVoice.name, sRate, sPitch, "\p");
-
- d = gRefSpeech->callbacks->CenterDialog(128);
- if (!d) return;
- gRefSpeech->d = d;
-
- SetPort(d); SelectWindow(d);
-
- err = GetSpeechInfo(gRefSpeech->channel, soCharacterMode, &t);
- SetDlgCtl( d, iCharMode, (t =='LTRL')); /* Don't spell text */
-
- MoveHHi(text); HLock(text);
- copy = gRefSpeech->callbacks->Allocate((Size) 0, true);
- if (MemError() != noErr) {
- gRefSpeech->callbacks->ReportOSError(MemError());
- return;
- }
-
- err = PtrAndHand(*text + selStart, copy, size); /* Make copy of selected text */
- HUnlock(text);
- if (MemError() != noErr) {
- gRefSpeech->callbacks->ReportOSError(MemError());
- return;
- }
-
- MoveHHi(copy); HLock(copy);
- TheTextPtr = *copy;
- SpeakText(gRefSpeech->channel, (Ptr)TheTextPtr, size); /* Then speak the text through the channel */
-
- while(gRefSpeech->done == false) {
-
- ModalDialog(filter, &item);
-
- switch(item) {
-
- case iTalk: SpeakText(gRefSpeech->channel, (Ptr)TheTextPtr, size); /* Then speak the text through the channel */
- break;
-
- case iStop: err = GetSpeechInfo (gRefSpeech->channel, soStatus, &gRefSpeech->SInfo);
- if (gRefSpeech->SInfo.outputPaused)
- err = StopSpeech(gRefSpeech->channel);
- else err = StopSpeechAt(gRefSpeech->channel, kEndOfWord);
- break;
-
- case iCont: err = ContinueSpeech(gRefSpeech->channel);
- break;
-
- case iPause: err = PauseSpeechAt(gRefSpeech->channel, kEndOfWord);
- break;
-
- case iCharMode:
- gRefSpeech->spellit = GetDlgCtl( d, item); /* True = talk, false = spell text */
- t = gRefSpeech->spellit ? 'NORM' : 'LTRL';
- err = SetSpeechInfo (gRefSpeech->channel, soCharacterMode, &t);
- SetDlgCtl( d, item, !GetDlgCtl( d, item));
- break;
-
- case iExit: if (SpeechBusy() != 0) stop_speech(gRefSpeech->channel);
- gRefSpeech->done = true;
- break;
- } /* End of Switch */
-
- } /* End of While() */
-
- HUnlock(copy);
- DisposHandle(copy);
- DisposDialog(d);
- } /* End of () */
-
- #else
-
- /******************************************************************************************
-
- 1) No dialog just talk the selected text
- 2) Exit when done talking or user click mouse or presses a key.
- */
-
- void HandleDialog(Handle text, long selStart, Size size)
- { OSErr err;
- Ptr TheTextPtr;
- EventRecord choice;
- Boolean done = false;
-
- long stackSp;
-
- stackSp = StackSpace();
-
- #ifdef StackDebug
- { Str15 tmpString;
-
- NumToString(stackSp, tmpString);
- DebugStr(tmpString);
- }
- #endif
-
- MoveHHi(text); HLock(text);
-
- TheTextPtr = *text + selStart;
- err = SpeakText(gRefSpeech->channel, (Ptr)TheTextPtr, size); /* Then speak the text through the channel */
-
- while(!done) {
-
- WaitNextEvent((mDownMask | keyDownMask), &choice, 15, nil);
-
- switch (choice.what) {
- case mouseDown:
- case keyDown:
- if (SpeechBusy() != 0) stop_speech(gRefSpeech->channel);
- done = true;
- break;
- }
-
- if (SpeechBusy() == 0) done = true;
- } /* End of While() */
-
- HUnlock(text);
-
- }
- #endif
-
- /******************************************************************************************
-
- 1) Check if speech manager is installed
- 2) check a BBEdit window exists
- 3) Get the text of the BBEdit window
- 4) Setup Speech manger voice channel.
- 5) Talk the selected text.
- 6) clean up.
- */
-
- pascal void main( ExternalCallbackBlock *callbacks, WindowPtr w)
- { OSErr err;
- Handle text;
- Size size;
- Ptr TheTextPtr;
- long selStart, selEnd;
- Handle h;
- GrafPtr save_port;
- #ifdef StackDebug
- long stackSp;
- Str15 tmpString;
- #endif
-
- RememberA0(); SetUpA4();
- GetPort(&save_port);
-
- if ( callbacks->version < 2 ||
- !w ||
- !SpeechInstalled(callbacks)) {
- SysBeep(0);
- RestoreA4();
- return; /* Exit no Speech Manager installed */
- }
-
- h = callbacks->Allocate((Size) sizeof(struct RefSpeak), true);
- if (!h) { RestoreA4(); return; };
-
- MoveHHi(h); HLock(h);
- gRefSpeech = (struct RefSpeak *) *h;
-
- text = callbacks->GetWindowContents( w );
- #if 1
- size = GetHandleSize(text);
- #else
- asm { /* don't need MacTrap library this way. */
- movea.l text, a0
- _GetHandleSize
- move.l d0, size
- }
- #endif
-
- callbacks->GetSelection(&selStart, &selEnd, &gRefSpeech->firstChar);
-
- gRefSpeech->selStart = selStart; /* Save user Selection */
- gRefSpeech->selEnd = selEnd;
- gRefSpeech->callbacks = callbacks;
- gRefSpeech->w = w;
- gRefSpeech->d = nil;
- gRefSpeech->done = false;
-
- if (selStart == selEnd) { /* No text select - select whole document */
- selStart = 0;
- selEnd = size;
- }
-
- size = selEnd - selStart;
- if (size == 0) {
- SysBeep(0);
- goto CleanExit;
- } /* No Text - Beep... */
-
- gRefSpeech->TTSParams.flags = ttsMaleVoices | ttsFemaleVoices;
- gRefSpeech->TTSParams.rate = 0; /* use default pitch, rate, modulation */
- gRefSpeech->TTSParams.pitch = 0;
- gRefSpeech->TTSParams.modulation = -1;
-
- err = DoSetTTS(gRefSpeech); /* If command key is press no selection */
-
- err = NewSpeechChannel(&gRefSpeech->TTSParams.curVoice.voice, &gRefSpeech->channel); /* Open a SpeechChannel */
-
- if (err != noErr) { /* Could not open SpeechChannel: exiting. */
- callbacks->ReportOSError(err);
- SysBeep(0);
- goto CleanExit;
- }
-
- SetTalkOptions(gRefSpeech); /* setup callbacks and speaking options */
-
- if (CommandDown()) { /* Messy fix so scrolling into VIEW the SELECTED WORD updates */
- Handle h; /* so allow user to override if command key is pressed down */
-
- SetPort(w);
- callbacks->SetSelection(selStart, selStart, -1);
-
- h = callbacks->Copy();
- callbacks->Paste(h);
- DisposHandle(h);
- }
-
- HandleDialog(text, selStart, size); /* Go play the Text */
-
- DisposeSpeechChannel(gRefSpeech->channel); /* clean up BEFORE EXITING. */
-
- CleanExit:
- SetPort(w); /* Restore user Selection */
- callbacks->SetSelection(gRefSpeech->selStart, gRefSpeech->selEnd, -1);
- SetPort(save_port);
- DisposHandle(h);
- RestoreA4();
- } /* End of Main() */
-